﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CLUBsInterpreter.AST;
using CLUBsInterpreter.ObjectTypes;
using System.IO;

namespace CLUBsInterpreter
{
	/**
	 * <summary>
	 * Describes an identification table/tree used in contextual analysis of a CLUBs program.
	 * Extends dictionary of string/Identification pairs.
	 * </summary>
	 **/
	class IdentificationTable : Dictionary<string, Identification>
	{
		/**
		 * <summary>
		 * Private field keeping track of the on-the-fly "temporary" scopes.
		 * (Scopes used for unnamed blocks like in control structures.)
		 * </summary>
		 **/
		private uint m_tmpScopeCounter = 0;

		/**
		 * <summary>
		 * The parent scope.
		 * </summary>
		 **/
		public IdentificationTable Parent;

		public string Name;
		/**
		 * <summary>
		 * The closing path of this scope.
		 * This is set when opening a new scope, because sometimes it is desirable to enter a scope that is defined at a lower scope level.
		 * </summary>
		 **/
		public IdentificationTable ClosePath;
		/**
		 * <summary>
		 * The subscopes of this table.
		 * </summary>
		 **/
		public Dictionary<string, IdentificationTable> SubScopes = new Dictionary<string, IdentificationTable>();
		/**
		 * <summary>
		 * The scope level of this table. (0 is global scope.)
		 * </summary>
		 **/
		public uint ScopeLevel;

		/**
		 * <summary>
		 * Constructor takes a parent table, unless it is in the root scope.
		 * </summary>
		 **/
		public IdentificationTable(string name, IdentificationTable par = null)
			: base()
		{
			Name = name;
			// Set the parent table
			Parent = par;
			// If the parent was null, set this table's scope level to 0
			if (par == null) ScopeLevel = 0;
			// If not, set this scope level to that of the parent +1
			else ScopeLevel = par.ScopeLevel + 1;
		}

		/**
		 * <summary>
		 * Opens a scope from name, with the possibility of creating the scope if it doesn't exist.
		 * </summary>
		 **/
		public IdentificationTable OpenScope(string scopeName, bool createScope = false)
		{
			// We're supposed to create a scope
			if (createScope)
			{
				// If we already have the scope in our local block
				if (SubScopes.ContainsKey(scopeName))
				{
					// Set the target scope's close path
					SubScopes[scopeName].ClosePath = this;
					// And return the target scope
					return SubScopes[scopeName];
				}
				// If the scope was not defined locally
				else
				{
					// We create a new subtable with ourselves as parent
					IdentificationTable ret = new IdentificationTable(scopeName, this);
					// Set the new table's close path
					ret.ClosePath = this;
					// Add the new table to our local scope
					SubScopes.Add(scopeName, ret);
					// And return the new table
					return ret;
				}
			}
			// If we're not supposed to create a new scope
			else
			{
				// Start by looking in this local scope
				IdentificationTable par = this;
				// Until we reach the root of the tree
				while (par != null)
				{
					// Check if the local scope contains the scope we're looking for
					if (par.SubScopes.ContainsKey(scopeName))
					{
						// In which case we set its close path
						par.SubScopes[scopeName].ClosePath = this;
						// And return the found scope
						return par.SubScopes[scopeName];
					}
					// If not, we move one step up the tree
					par = par.Parent;
				}

				// If this point is reached, we will have been unable to find the subscope
				throw new ScopeNotFoundException(scopeName);

			}
		}

		/**
		 * <summary>
		 * Opens a scope from block, with the possibility of creating the scope if it doesn't exist.
		 * </summary>
		 **/
		public IdentificationTable OpenScope(BlockNode scopeBlock, bool createScope = false)
		{
			// If the block is unnamed
			if (scopeBlock.BlockName == null)
			{
				// If we're not supposed to create a scope
				if (!createScope)
				{
					// Throw a scope creation exception, since we can not open an unnamed scope, only create it, and we're not allowed to
					throw new ScopeCreationException();
				}
				// Generate scope name
				string tmpName = "__tmp" + m_tmpScopeCounter;
				// Set the incoming block's name
				scopeBlock.BlockName = tmpName;
				// Add new block in the incoming scope, with the block's name
				SubScopes.Add(tmpName, new IdentificationTable(tmpName, this));
				// Increment the temporary scope counter
				m_tmpScopeCounter++;
				// Set the close path of the new scope
				SubScopes[tmpName].ClosePath = this;
				// And return it
				return SubScopes[tmpName];
			}
			// If the block is named
			else
			{
				// Call the string-based OpenScope function with the incoming block's name
				return OpenScope(scopeBlock.BlockName, createScope);
			}
		}

		/**
		 * <summary>
		 * Closes the current scope.
		 * </summary>
		 **/
		public IdentificationTable CloseScope()
		{
			// Check if the close path is null
			if (ClosePath == null)
				// In which case, throw a scope bounds exception
				throw new ScopeBoundsException();
			// If it wasn't null, return the next scope in the close path
			return ClosePath;
		}

		/**
		 * <summary>
		 * Prints this table and all subscopes.
		 * </summary>
		 **/
		public void PrintTree(TextWriter outStream)
		{
			// Output identification tree info
			outStream.WriteLine(AbstractSyntaxTree.TabDepth((int)ScopeLevel) + "-------------------------------------");
			outStream.WriteLine(AbstractSyntaxTree.TabDepth((int)ScopeLevel) + "Identification tree at scope level " + ScopeLevel);
			outStream.WriteLine(AbstractSyntaxTree.TabDepth((int)ScopeLevel) + "-------------------------------------");

			// Iterate over all identifications of this scope
			foreach (KeyValuePair<string, Identification> pair in this)
			{
				// Printing their type and name
				outStream.WriteLine(AbstractSyntaxTree.TabDepth((int)ScopeLevel) + pair.Value.IdentType + " " + pair.Key);
			}

			// Iterate over all subscopes
			foreach (KeyValuePair<string, IdentificationTable> pair in SubScopes)
			{
				// Printing a scope entry message
				outStream.WriteLine(AbstractSyntaxTree.TabDepth((int)ScopeLevel) + "Entering scope: " + pair.Key);
				// And printing the subtree
				pair.Value.PrintTree(outStream);
			}
		}
		/**
		 * <summary>
		 * Clears the tree.
		 * NOTE: THIS IS DISABLED BECAUSE OF A FLAW IN THE CLUBsObjects' INTERNAL REFERENCES
		 * They need to consider scopes when accessing local variables.
		 * Until this is implemented, it is possible to use the identification table, though it is not recommended.
		 * </summary>
		 **/
		public void ClearTree()
		{
			Clear();
			SubScopes.Clear();
		}

		public void SetObjectParents(CLUBsObject par = null)
		{
			if (par != null)
			{
				foreach (Identification id in base.Values)
				{
					if (id.Value != null)
						id.Value.LocalVariables.Parent = par.LocalVariables;
				}
			}

			foreach (KeyValuePair<string, IdentificationTable> s in SubScopes)
			{
				if (this.ContainsKey(s.Key))
					s.Value.SetObjectParents(this[s.Key].Value);
				else
					s.Value.SetObjectParents(par);
			}
		}

		// INDEXING FUNCTIONS
		/**
		 * <summary>
		 * Indexer method checks lower-level scopes if the ID could not be found in this local table.
		 * </summary>
		 **/
		public new Identification this[string id]
		{
			get
			{
				// If this local scope contains the id, return it
				if (base.ContainsKey(id))
				{
					return base[id];
				}
				// If not, and if we have a parent, try to find the id in the parent and return it
				else if (Parent != null)
				{
					return Parent[id];
				}
				// If we have no parent, throw an exception
				else
				{
					throw new KeyNotFoundException(id);
				}
			}
			set
			{
				// Set the id in the local scope
				base[id] = value;
				// And set the new id's scopelevel automatically
				base[id].Level = ScopeLevel;
			}
		}

		/**
		 * <summary>
		 * Override of dictionary's ContainsKey looks in lower-level scopes if the key is not found in the local table.
		 * </summary>
		 **/
		public new bool ContainsKey(string key)
		{
			// If the key exists locally, return it
			if (base.ContainsKey(key))
			{
				return true;
			}
			// If not, and we have a parent, try in the parent scope
			else if (Parent != null)
			{
				return Parent.ContainsKey(key);
			}
			// If we have no parent, return false
			else
			{
				return false;
			}
		}

		public Dictionary<string, CLUBsObject> GetScopeOf(string key)
		{
			// If the key exists locally, return the parent scope
			if (base.ContainsKey(key))
			{
				if (Parent != null)
					return Parent[Name].Value.LocalVariables;
				else
					return null;
			}
			// If not, and we have a parent, try in the parent scope
			else if (Parent != null)
			{
				return Parent.GetScopeOf(key);
			}
			// If we have no parent, return false
			else
			{
				throw new ScopeNotFoundException("Could not find scope of identifier "+key);
			}
		}

	}

	/**
	 * <summary>
	 * Describes an identification in the CLUBs environment.
	 * This should only be used during contextual analysis.
	 * </summary>
	 **/
	class Identification
	{
		/**
		 * <summary>
		 * The scope level of this identification.
		 * </summary>
		 **/
		public uint Level;
		/**
		 * <summary>
		 * The identifier type of this identification.
		 * </summary>
		 **/
		public IdentifierType IdentType;
		/**
		 * <summary>
		 * The actual object reference identified by this identification.
		 * </summary>
		 **/
		public CLUBsObject Value;
	}

	/**
	 * <summary>
	 * ScopeBoundsException is thrown if the contextual analyzer tries to move outside the bounds of the program scope.
	 * Ex., if it tries to close the root scope.
	 * </summary>
	 **/
	class ScopeBoundsException : Exception { }

	/**
	 * <summary>
	 * ScopeCreationException is thrown if the interpreter attempts to create a scope that already exists locally.
	 * </summary>
	 **/
	class ScopeCreationException : Exception { }

	/**
	 * <summary>
	 * ScopeNotFoundException is thrown if a subscope could not be found within the current scope..
	 * </summary>
	 **/
	class ScopeNotFoundException : Exception
	{
		public ScopeNotFoundException(string message)
			: base(message)
		{}
	}
}
